/**
 * 
 */
package com.netflexitysolutions.amazonws.sdb.orm.internal.operations;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;

import com.netflexitysolutions.amazonws.sdb.Item;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.AttributeMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.IdMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ManyToManyMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ManyToOneMetadata;

/**
 * @author netflexity
 *
 */
public class SimpleDBSaveItemSetter <T> implements ItemSetter<T>{

	private ItemMetadata itemMetadata;
	private boolean update = false;
	
	private Map<String, Item> nullableItemAttributes = new HashMap<String, Item>();
	private Map<String, Item> changedItemAttributes = new HashMap<String, Item>();
	
	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.internal.operations.ItemSetter#setValues(java.util.List)
	 */
	public void setValues(List<T> records){
		for (T record : records) {
			setValue(record);
		}
	}
	
	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.internal.operations.ItemSetter#setValue(T)
	 */
	public String setValue(T record){
		// Get or create new item identifier.
		String identifier = addId(record);
		
		// Add logical name, which is a discriminator value for tables within a single domain.
		addLogicalName(identifier);
		
		// Add regular attributes.
		addAttributes(identifier, record);
		
		// Add Many-to-One attributes.
		addManyToOne(identifier, record);
		
		// Add Many-to-Many attributes.
		addManyToMany(identifier, record);
		
		return identifier;
	}

	/**
	 * @param record
	 * @return
	 */
	public String addId(T record){
		IdMetadata idMeta = itemMetadata.getId();
		Object identifier = idMeta.getFieldValue(record);
		if(identifier == null){
			identifier = UUID.randomUUID().toString();
			idMeta.setFieldValue(record, identifier);
		}
		return String.valueOf(identifier);
	}
	
	/**
	 * @param identifier
	 */
	public void addLogicalName(String identifier){
		if(StringUtils.isNotBlank(itemMetadata.getLogicalName())){
			addAttributeToSave(identifier, itemMetadata.getLogicalNameColumnName(), itemMetadata.getLogicalName(), true);
		}
	}
	
	/**
	 * @param identifier
	 * @param record
	 */
	public void addAttributes(String identifier, T record){
		List<AttributeMetadata> attributeMetadata = itemMetadata.getAttributeMetadata();
		for (AttributeMetadata meta : attributeMetadata) {
			String value = meta.toSimpleDB(record);
			if(StringUtils.isNotBlank(value)){
				addAttributeToSave(identifier, meta.getColumnName(), value, true);
			}
			// No need to delete anything if new object, just when updating.
			else if(isUpdate()){
				addAttributeToDelete(identifier, meta.getColumnName(), null, true);
			}
		}
	}
	
	/**
	 * @param identifier
	 * @param record
	 */
	public void addManyToOne(String identifier, T record){
		List<ManyToOneMetadata> manyToOneMetadata = itemMetadata.getManyToOne();
		for (ManyToOneMetadata m2o : manyToOneMetadata) {
			String value = m2o.toSimpleDB(record);
			if(StringUtils.isNotBlank(value)){
				addAttributeToSave(identifier, m2o.getColumnName(), value, true);
			}
			// No need to delete anything if new object, just when updating.
			else if(isUpdate()){
				addAttributeToDelete(identifier, m2o.getColumnName(), null, true);
			}
		}
	}
	
	/**
	 * Setting first attribute with replace option will reset the list and prepare for new set of items.
	 * 
	 * @param identifier
	 * @param record
	 */
	public void addManyToMany(String identifier, T record){
		List<ManyToManyMetadata> manyToManyMetadata = itemMetadata.getManyToMany();
		for (ManyToManyMetadata m2m : manyToManyMetadata) {
			Collection<Object> m2mBeanSet = (Collection<Object>)m2m.getFieldValue(record);
			boolean first = true;
			for (Object m2mBean : m2mBeanSet) {
				String value = m2m.toSimpleDB(m2mBean);
				if(StringUtils.isNotBlank(value)){
					addAttributeToSave(identifier, m2m.getColumnName(), value, first);
				}
				if(first){
					first = false;
				}
			}
		}
	}
	
	/**
	 * Store item attributes with values.
	 * 
	 * @param identifier
	 * @param attribute
	 */
	protected void addAttributeToSave(String identifier, String name, String value, boolean replace){
		Item itemToSave = changedItemAttributes.get(identifier);
		if(itemToSave == null){
			itemToSave = new Item();
			itemToSave.setIdentifier(identifier);
			changedItemAttributes.put(identifier, itemToSave);
		}
		itemToSave.addAttribute(name, value, replace);
	}
	
	/**
	 * Store item attributes without values.
	 * 
	 * @param identifier
	 * @param attribute
	 */
	protected void addAttributeToDelete(String identifier, String name, String value, boolean replace){
		Item itemToDelete = nullableItemAttributes.get(identifier);
		if(itemToDelete == null){
			itemToDelete = new Item();
			itemToDelete.setIdentifier(identifier);
			nullableItemAttributes.put(identifier, itemToDelete);
		}
		itemToDelete.addAttribute(name, value, replace);
	}
	
	/**
	 * @return the itemMetadata
	 */
	public ItemMetadata getItemMetadata() {
		return itemMetadata;
	}

	/**
	 * @param itemMetadata the itemMetadata to set
	 */
	public void setItemMetadata(ItemMetadata itemMetadata) {
		this.itemMetadata = itemMetadata;
	}
	
	/**
	 * @return the update
	 */
	public boolean isUpdate() {
		return update;
	}

	/**
	 * @param update the update to set
	 */
	public void setUpdate(boolean update) {
		this.update = update;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.internal.operations.ItemSetter#getNullableItemAttributes()
	 */
	public Map<String, Item> getNullableItemAttributes() {
		return nullableItemAttributes;
	}

	/* (non-Javadoc)
	 * @see com.netflexitysolutions.amazonws.sdb.orm.internal.operations.ItemSetter#getChangedItemAttributes()
	 */
	public Map<String, Item> getChangedItemAttributes() {
		return changedItemAttributes;
	}
}
